package org.zoomdev.zoom.aop.impl;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.Modifier;
import javassist.NotFoundException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.zoomdev.zoom.aop.ProxyFactory;
import org.zoomdev.zoom.aop.ProxyObject;
import org.zoomdev.zoom.aop.utils.ClassBuilder;
import org.zoomdev.zoom.common.exceptions.ZoomException;
import org.zoomdev.zoom.common.utils.CachedClasses;
import org.zoomdev.zoom.common.utils.Classes;
import org.zoomdev.zoom.common.utils.Utils;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

public class ProxyFactoryImpl implements ProxyFactory {


    private static final String TAIL = "$Proxy";
    private static final Log log = LogFactory.getLog(ProxyFactoryImpl.class);
    private final ClassPool classPool;

    private final AtomicInteger counter = new AtomicInteger();

    public ProxyFactoryImpl() {
        this(ClassPool.getDefault());
    }


    public ProxyFactoryImpl(ClassPool classPool) {
        this.classPool = classPool;
    }

    @Override
    public <T> T createObject(Class<T> interfaceClass, ProxyObject object) {
        try {
            Class enhanced = createClass(interfaceClass);
            Object ret;
            try {
                ret = Classes.newInstance(enhanced);
            } catch (Exception e) {
                if (!interfaceClass.isInterface()) {
                    throw new ZoomException("创建对象失败，请检查抽象类" + interfaceClass.getName() + "是否是内部非static类,导致无法创建", e);
                }

                throw e;
            }

            Utils.set(enhanced, ret, "$_obj", object);
            initInstance(enhanced, ret, interfaceClass);
            return (T) ret;
        } catch (Exception e) {
            Throwable t = Classes.getCause(e);
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            }
            throw new ZoomException(e);
        }

    }


    @Override
    public Class createClass(Class interfaceOrAbstractClass) {

        String className = interfaceOrAbstractClass.getName() + TAIL;
        Class enhanced;
        try {
            enhanced = Class.forName(className, false, interfaceOrAbstractClass.getClassLoader());
            if (log.isDebugEnabled()) {
                log.debug("类已经存在，直接返回" + enhanced);
            }
            //重新初始化一下
        } catch (ClassNotFoundException ex) {
            try {
                enhanced = execute(interfaceOrAbstractClass, className);
                Classes.set(enhanced, "$_class", interfaceOrAbstractClass);
            } catch (Exception e) {
                throw new ZoomException("创建类失败", Classes.getCause(e));
            }
        }
        return enhanced;
    }

    private void initInstance(Class enhanced, Object instance, Class interfaceOrAbstractClass) throws Exception {
        Method[] methods = getMethods(interfaceOrAbstractClass);
        int index = 0;
        for (Method method : methods) {
            Utils.set(enhanced, instance, "$mthd" + index, method);
            ++index;
        }
    }


    public Method[] getMethods(Class interfaceOrAbstractClass) {
        if (interfaceOrAbstractClass.isInterface()) {
            return CachedClasses.getPublicMethods(interfaceOrAbstractClass);
        } else if (Utils.isAbstract(interfaceOrAbstractClass)) {
            Method[] ms = CachedClasses.getPublicMethods(interfaceOrAbstractClass);
            List<Method> list = new ArrayList<>();
            for (Method m : ms) {
                if (Modifier.isAbstract(m.getModifiers())) {
                    list.add(m);
                }
            }
            return list.toArray(new Method[list.size()]);
        } else {
            throw new ZoomException("非接口或者抽象类无法进行代理");
        }
    }

    private Class execute(Class interfaceOrAbstractClass, String className) throws CannotCompileException, NotFoundException {
        ClassBuilder builder = new ClassBuilder(interfaceOrAbstractClass.getClassLoader(), classPool, className);
        try {
            builder.findClass();
            return builder.toClass();
        } catch (NotFoundException e) {
            builder.createClass();

            if (interfaceOrAbstractClass.isInterface()) {
                builder.addInterface(interfaceOrAbstractClass);
            } else if (Utils.isAbstract(interfaceOrAbstractClass)) {
                builder.setSuperClass(interfaceOrAbstractClass);
                builder.addConstructor(new Class[0], "");
            } else {
                throw new ZoomException("非接口或者抽象类无法进行代理");
            }
            Method[] methods = getMethods(interfaceOrAbstractClass);

            builder.addField(Class.class, "$_class", Modifier.PROTECTED | Modifier.STATIC);
            //将来new出来之后要赋值
            builder.addField(ProxyObject.class, "$_obj", Modifier.PRIVATE);
            int index = 0;
            for (Method method : methods) {
                builder.addField(Method.class, "$mthd" + index, Modifier.STATIC | Modifier.PROTECTED);
                builder.addField(Object.class, "$cfg" + index, Modifier.PRIVATE);
                builder.addMethod("_create" + index, void.class, createBody(index));

                builder.addMethod(method, createBody(method, index));
                ++index;
            }
        }

        return builder.toClass();
    }

    // public static final String BODY = "if($cfg0==null){synchronized ($mthd0){if($cfg0==null){$cfg0 = $_obj.create($0,$mthd0);}}}";


    private String createBody(int index) {
        return "synchronized ($mthd1){if($cfg1==null){$cfg1 = $_obj.create($_class,$0,$mthd1);}}".replace("1", String.valueOf(index));
    }

    private String createBody(Method method, int index) {
        String str1 = "if($cfg0==null){_create0();}".replace("0", String.valueOf(index));
        return str1 + (String.format("return ($r)$_obj.invoke($cfg%d, $args);", index));
    }
}
